import constant from "./constant";

const live2d_path = constant.live2d_path;  // 注意：live2d_path 参数应使用绝对路径

// 在屏幕宽度大于768像素时，动态加载外部资源（CSS和JavaScript文件）
if (screen.width > 768) {
  Promise.all([
    loadExternalResource(live2d_path + "waifu.css", "css"),
    loadExternalResource(live2d_path + "live2d.min.js", "js")
  ]).then(() => {
    // 在资源加载完成后初始化一个Live2D小部件
    initWidget({
      waifuPath: constant.baseURL + constant.waifuPath,
      cdnPath: constant.cdnPath
    });
  });
}
/**
* @author DavidLee 
* @title loadExternalResource
* @date 2024-10-11
* @description 异步动态加载外部的CSS或JavaScript资源
* @param  url：资源的URL地址
* @param  type：资源的类型，可以是"css"或"js"
* @return null
*/
function loadExternalResource(url, type) {
  // 创建Promise对象：对象在资源加载成功时调用resolve，在加载失败时调用reject
  return new Promise((resolve, reject) => {
    let tag;  
    // 根据传入的type参数，创建一个<link>标签（用于CSS）或<script>标签（用于JavaScript）
    if (type === "css") {
      tag = document.createElement("link");
      tag.rel = "stylesheet";               // 设置<link>标签的rel属性为stylesheet
      tag.href = url;                       // 设置<link>标签的href属性为传入的url参数
    } else if (type === "js") {
      tag = document.createElement("script");
      tag.src = url;                        // 设置<script>标签的src属性为传入的url参数
    }

    if (tag) {      // 如果成功创建了标签
      tag.onload = () => resolve(url);   // 当资源加载成功时，调用resolve函数，并传入资源的URL
      tag.onerror = () => reject(url);   // 当资源加载失败时，调用reject函数，并传入资源的URL
      document.head.appendChild(tag);    // 将标签添加到文档的<head>部分
    }
  });
}

/**
* @author DavidLee 
* @title initWidget
* @date 2024-10-11
* @description 初始化一个名为“看板娘”的悬浮小部件。这个小部件可以通过点击一个按钮来显示或隐藏
* @param  config：配置参数
* @return null
*/
function initWidget(config) {
  // 在<body>标签的末尾插入一个<div>元素，该元素具有id="waifu-toggle"，并包含一个<span>元素，显示文本“看板娘”
  document.body.insertAdjacentHTML("beforeend", `<div id="waifu-toggle">
			<span>看板娘</span>
		</div>`);
  const toggle = document.getElementById("waifu-toggle");  // 通过ID选择器获取插入的<div>元素
  toggle.addEventListener("click", () => {                 // 添加一个点击事件监听器
    toggle.classList.remove("waifu-toggle-active");        // 当点击事件发生时，移除waifu-toggle-active类
    if (toggle.getAttribute("first-time")) {
      // 如果有first-time属性（第一次点击），调用loadWidget函数加载小部件，并移除first-time属性
      loadWidget(config);
      toggle.removeAttribute("first-time");
    } else {
      // 如果不是第一次点击，则从本地存储中移除waifu-display项，并将id="waifu"的元素的display属性设置为空字符串，使其可见
      localStorage.removeItem("waifu-display");
      document.getElementById("waifu").style.display = "";
      // 通过setTimeout将bottom样式属性设置为0，使小部件从底部滑出
      setTimeout(() => {
        document.getElementById("waifu").style.bottom = 0;
      }, 0);
    }
  });
  // 检查本地存储中是否存在waifu-display项，如果存在且时间间隔小于一天，则显示小部件
  if (localStorage.getItem("waifu-display") && Date.now() - localStorage.getItem("waifu-display") <= 86400000) {
    toggle.setAttribute("first-time", true);
    // 在0毫秒后添加waifu-toggle-active类，使按钮显示为激活状态
    setTimeout(() => {
      toggle.classList.add("waifu-toggle-active");
    }, 0);
  } else {
    // 如果不存在waifu-display项或存在时间间隔大于一天，则直接调用loadWidget函数加载小部件
    loadWidget(config);
  }
}

/**
* @author DavidLee 
* @title loadWidget
* @date 2024-10-11
* @description 加载名为“看板娘”的悬浮小部件，包含加载时的各种属性和操作
* @param  config：配置参数
* @return null
*/
function loadWidget(config) {
  let {waifuPath, cdnPath} = config;    // 配置路径
  if (!cdnPath.endsWith("/")) cdnPath += "/";
  let modelList, idx = 0;
  localStorage.removeItem("waifu-display"); // 移除本地存储中的waifu-display项和waifu-text项
  localStorage.removeItem("waifu-text");
  // 添加了一个包含Live2D模型的<div>元素以及一些工具按钮
  // <div id="waifu">：这是最外层的<div>元素，其id为waifu，用于包裹整个L2D模型和工具按钮
  // <div id="waifu-tips">：这是用于显示L2D模型的提示信息，id为waifu-tips
  // <canvas id="live2d">：这是一个<canvas>元素，用于绘制L2D模型，id为live2d，宽度和高度都设置为800像素
  // <div id="waifu-tool">：这个<div>元素包含了一些工具按钮，id为waifu-tool
      // <span class="fa fa-lg fa-comment"></span>：这是一个评论按钮，使用了Font Awesome图标库，class属性指定了图标样式
      // <span class="fa fa-lg fa-street-view"></span>：这是一个街景按钮，同样使用了Font Awesome图标库
      // <span class="fa fa-lg fa-mouse-pointer"></span>：这是一个鼠标指针按钮，同样使用了Font Awesome图标库
      // <span class="fa fa-lg fa-times"></span>：这是一个关闭按钮，同样使用了Font Awesome图标库
  document.body.insertAdjacentHTML("beforeend", `<div id="waifu">   
			<div id="waifu-tips"></div>
			<canvas id="live2d" width="800" height="800"></canvas>
      <!-- 工具 -->
			<div id="waifu-tool">
				<span class="fa fa-lg fa-comment"></span>
				<span class="fa fa-lg fa-street-view"></span>
				<span class="fa fa-lg fa-mouse-pointer"></span>
				<span class="fa fa-lg fa-times"></span>
			</div>
		</div>`);
  // 让一个ID为"waifu"的元素在页面加载后立即移动到其包含块的底部。这通常用于动画效果
  setTimeout(() => {
    document.getElementById("waifu").style.bottom = 0;
  }, 0);

  // 检测用户活动状态，并在空闲时显示消息
  let userAction = false,   // 用于标记用户是否进行了操作
    userActionTimer,        // 定时器，用于在用户无操作时触发消息显示
    messageTimer,           // 定时器，用于控制消息显示的时间
    messageArray = ["好久不见，日子过得好快呢……", "大坏蛋！你都多久没理人家了呀，嘤嘤嘤～", "嗨～快来逗我玩吧！", "拿小拳拳锤你胸口！", "记得把小家加入 Adblock 白名单哦！"];
  window.addEventListener("mousemove", () => userAction = true);   // 监听mousemove和keydown事件
  window.addEventListener("keydown", () => userAction = true);
  setInterval(() => {           // 每秒（1000ms）检查一次用户活动状态
    // 如果用户有操作，则将userAction设置为false，并清除userActionTimer定时器
    if (userAction) {
      userAction = false;
      clearInterval(userActionTimer);
      userActionTimer = null;
    // 如果用户无操作且userActionTimer为空，则创建一个新的定时器，该定时器每20秒执行一次，执行时调用showMessage函数显示一条随机选择的消息
    } else if (!userActionTimer) {
      userActionTimer = setInterval(() => {
        showMessage(randomSelection(messageArray), 6000, 9);
      }, 20000);
    }
  }, 1000);

  /**
  * @author DavidLee 
  * @title registerEventListener
  * @date 2024-10-11
  * @description 注册监听器，用于处理网页上的特定元素点击事件，以及一些其他的事件
  * @param  null
  * @return null
  */
  (function registerEventListener() {
    // 给ID为waifu-tool元素下的类名为fa-comment的元素添加点击事件监听器，点击时调用 showHitokoto 函数，
    document.querySelector("#waifu-tool .fa-comment").addEventListener("click", showHitokoto);
    document.querySelector("#waifu-tool .fa-street-view").addEventListener("click", loadRandModel);
    document.querySelector("#waifu-tool .fa-mouse-pointer").addEventListener("click", changeMouseAnimation);
    // 为ID为waifu-tool元素下的类名为fa-times的元素添加点击事件监听器，点击时执行一系列操作：
    document.querySelector("#waifu-tool .fa-times").addEventListener("click", () => {
      localStorage.setItem("waifu-display", Date.now());     // 将waifu-display存储在localStorage中，值为当前时间戳
      showMessage("愿你有一天能与重要的人重逢。", 2000, 11);    // 显示一条消息，持续时间为2秒，消息优先级为11
      document.getElementById("waifu").style.bottom = "-500px";  // 将waifu元素移动到其包含块的底部
      // 3秒后，将ID为waifu的元素的显示状态设置为none，隐藏元素
      // 同时添加ID为waifu-toggle的元素的类名waifu-toggle-active元素
      setTimeout(() => {
        document.getElementById("waifu").style.display = "none";
        document.getElementById("waifu-toggle").classList.add("waifu-toggle-active");
      }, 3000);
    });

    const devtools = () => {   // 定义一个空函数devtools
    };
    console.log("%c", devtools);  // 在控制台输出一个占位符，后面跟随devtools函数
    devtools.toString = () => {   // 重写devtools函数的toString方法，
      // 当控制台输出devtools时，会显示一条消息，持续6秒，消息优先级为9
      showMessage("哈哈，你打开了控制台，是想要看看我的小秘密吗？", 6000, 9);  
    };
    window.addEventListener("copy", () => {
      showMessage("你都复制了些什么呀，转载要记得加上出处哦！", 6000, 9);  // 当用户复制内容时，显示一条消息，持续6秒，消息优先级为9
    });
    window.addEventListener("visibilitychange", () => {
      if (!document.hidden) showMessage("哇，你终于回来了～", 6000, 9);  // 当页面可见时，显示一条消息，持续6秒，消息优先级为9
    });

    localStorage.setItem("showMouseAnimation", "1");   // 将showMouseAnimation存储在localStorage中，值为1，可能用于控制是否显示鼠标动画
    document.querySelector("body").addEventListener("click", mouseAnimation);  // 为body元素添加点击事件监听器，点击时调用mouseAnimation函数
  })();

  /**
  * @author DavidLee 
  * @title registerEventListener
  * @date 2024-10-11
  * @description 欢迎页 根据用户访问的页面和访问来源，显示不同的欢迎信息
  * @param  null
  * @return null
  */
  (function welcomeMessage() {
    let text;
    if (location.pathname === "/") { // 检查当前页面的路径。如果路径是"/"，则认为用户访问的是主页
      // 使用new Date().getHours()获取当前的小时数，并根据小时数显示不同的欢迎信息
      const now = new Date().getHours();
      if (now > 5 && now <= 7) text = "早上好！一日之计在于晨，美好的一天就要开始了。";
      else if (now > 7 && now <= 11) text = "上午好！工作顺利嘛，不要久坐，多起来走动走动哦！";
      else if (now > 11 && now <= 13) text = "中午了，工作了一个上午，现在是午餐时间！";
      else if (now > 13 && now <= 17) text = "午后很容易犯困呢，今天的运动目标完成了吗？";
      else if (now > 17 && now <= 19) text = "傍晚了！窗外夕阳的景色很美丽呢，最美不过夕阳红～";
      else if (now > 19 && now <= 21) text = "晚上好，今天过得怎么样？";
      else if (now > 21 && now <= 23) text = ["已经这么晚了呀，早点休息吧，晚安～", "深夜时要爱护眼睛呀！"];
      else text = "你是夜猫子呀？这么晚还不睡觉，明天起的来嘛？";
    } else if (document.referrer !== "") {    // 如果用户不是直接访问主页，而是从其他页面跳转过来的，则根据访问来源显示不同的欢迎信息
      const referrer = new URL(document.referrer),
        domain = referrer.hostname.split(".")[1];
      if (location.hostname === referrer.hostname) text = `欢迎阅读<span>「${document.title.split(" - ")[0]}」</span>`;
      else if (domain === "baidu") text = `Hello！来自 百度搜索 的朋友<br>你是搜索 <span>${referrer.search.split("&wd=")[1].split("&")[0]}</span> 找到的我吗？`;
      else if (domain === "so") text = `Hello！来自 360搜索 的朋友<br>你是搜索 <span>${referrer.search.split("&q=")[1].split("&")[0]}</span> 找到的我吗？`;
      else if (domain === "google") text = `Hello！来自 谷歌搜索 的朋友<br>欢迎阅读<span>「${document.title.split(" - ")[0]}」</span>`;
      else text = `Hello！来自 <span>${referrer.hostname}</span> 的朋友`;
    } else {   
      text = `欢迎阅读<span>「${document.title.split(" - ")[0]}」</span>`;  // 如果用户是直接访问主页，则显示默认的欢迎信息。
    }
    showMessage(text, 7000, 8);    // 设置显示时间为7秒，字体大小为8
  })();

  /**
  * @author DavidLee 
  * @title registerEventListener
  * @date 2024-10-11
  * @description 初始化模型 根据用户的交互（鼠标悬停和点击）以及当前日期来显示不同的消息
  * @param  null
  * @return null
  */
  (function initModel() {
    let modelId = localStorage.getItem("modelId");  // 从本地存储中获取模型ID
    if (modelId === null) {
      modelId = 5; // 如果本地存储中没有模型ID，则默认设置为5
    }
    loadModel(modelId);   //  加载模型
    fetch(waifuPath)
      .then(response => response.json())    // 从指定的路径获取数据，并期望返回一个JSON对象
      .then(result => {
        window.addEventListener("mouseover", event => {     // 为window对象添加mouseover事件监听器，当鼠标悬停在元素上时触发
          for (let {selector, text} of result.mouseover) {  
            if (!event.target.matches(selector)) continue;         // 如果事件选择器没有匹配上当前事件则跳过本次循环
            text = randomSelection(text);                          // 从返回的JSON数据中随机选择一条消息
            text = text.replace("{text}", event.target.innerText); // 将当前元素的文本替换到消息{text}中
            showMessage(text, 4000, 8);                            // 显示替换的消息4秒，优先级设为8
            return;
          }
        });
        window.addEventListener("click", event => {         // 为window对象添加click事件监听器，当鼠标点击元素时触发
          for (let {selector, text} of result.click) {   
            if (!event.target.matches(selector)) continue;  // 如果事件选择器没有匹配上当前事件则跳过本次循环
            text = randomSelection(text);                          // 同上
            text = text.replace("{text}", event.target.innerText);
            showMessage(text, 4000, 8);
            return;
          }
        });
        result.seasons.forEach(({date, text}) => {    // 遍历result.seasons数组获取date和text属性
          const now = new Date(),                     // 获取当前日期
          // 假设date是一个字符串，格式为"MM/DD-YYYY"或"MM/DD"。split("-")方法将日期字符串分割成两个部分：after和before
            after = date.split("-")[0],               // 获取日期字符串的第一个部分
            before = date.split("-")[1] || after;     // 如果日期字符串没有第二个部分，则将after赋值给before
          // 检查当前日期是否在after和before定义的日期范围内
          if ((after.split("/")[0] <= now.getMonth() + 1 && now.getMonth() + 1 <= before.split("/")[0]) && (after.split("/")[1] <= now.getDate() && now.getDate() <= before.split("/")[1])) {
            text = randomSelection(text);                      // 从返回的JSON数据中随机选择一条消息
            text = text.replace("{year}", now.getFullYear());  // 将当前年份替换到消息{year}中
            messageArray.push(text);                           // 将处理后的text添加到messageArray数组
          }
        });
      });
  })();

  /**
  * @author DavidLee 
  * @title loadModelList 
  * @date 2024-10-11
  * @description 模型集合异步函数 从指定的URL加载一个JSON格式的模型列表
  * @param  null
  * @return null
  */
  async function loadModelList() {
    // 异步函数意味着函数内部可以使用await关键字来等待异步操作的结果，而不需要使用.then()方法
    const response = await fetch(`${cdnPath}model_list.json`);  // 使用fetch函数发送一个GET请求到${cdnPath}model_list.json
    modelList = await response.json();  // 使用await关键字等待fetch函数返回的Promise对象解析为Response对象，然后将Response对象解析为JSON对象，存储在全局变量modelList中
  }

  /**
  * @author DavidLee 
  * @title loadModel 
  * @date 2024-10-11
  * @description 载入模型
  * @param  modelId：表示要加载的模型的ID
  * @param  message：表示要显示的消息
  * @return null
  */
  async function loadModel(modelId, message) {
    localStorage.setItem("modelId", modelId);  // 保存模型ID到本地存储
    showMessage(message, 4000, 10);            // 显示消息4秒，优先级设为10
    if (!modelList) await loadModelList();     // 如果modelList为空，则调用loadModelList函数异步加载模型列表
    // const target = randomSelection(modelList.models[modelId]);
    const target = "HyperdimensionNeptunia/blanc_swimwear";        // 模型ID硬编码为该字符串
    loadlive2d("live2d", `${cdnPath}model/${target}/index.json`);  // 加载live2d模型，模型的url为后面的字符串
  }

  /**
  * @author DavidLee 
  * @title loadModel 
  * @date 2024-10-11
  * @description 加载一个随机的Live2D模型 换肤
  * @param  null
  * @return null
  */
  async function loadRandModel() {
    const modelId = localStorage.getItem("modelId");   // 获取本地存储中的模型ID
    if (!modelList) await loadModelList();             // 如果modelList为空，则调用loadModelList函数异步加载模型列表
    const target = randomSelection(modelList.models[modelId]);    // 从modelList.models[modelId]中随机选择一个模型硬编码字符串
    loadlive2d("live2d", `${cdnPath}model/${target}/index.json`); // 加载live2d模型，模型的url为后面的字符串
    showMessage("我的新衣服好看嘛？", 4000, 10);                    // 显示消息4秒，优先级设为10
  }

  /**
  * @author DavidLee 
  * @title loadOtherModel 
  * @date 2024-10-11
  * @description 加载其他模型 换人
  * @param  null
  * @return null
  */
  async function loadOtherModel() {
    let modelId = localStorage.getItem("modelId");  // 获取本地存储中的模型ID
    if (!modelList) await loadModelList();          // 如果modelList为空，则调用loadModelList函数异步加载模型列表
    const index = (++modelId >= modelList.models.length) ? 0 : modelId; // 将modelId加1，如果大于等于modelList.models.length，则将modelId设为0，否则保持不变
    loadModel(index, modelList.messages[index]);    // 载入模型，输入参数一为模型ID，输入参数二为模型消息
  }

  /**
  * @author DavidLee 
  * @title changeMouseAnimation 
  * @date 2024-10-11
  * @description 切换网页中鼠标动画的显示状态
  * @param  null
  * @return null
  */
  function changeMouseAnimation() {
    if (localStorage.getItem("showMouseAnimation") === "0") {   // 检查本地存储中的showMouseAnimation是否为0，若是表示当前鼠标动画是关闭状态
      localStorage.setItem("showMouseAnimation", "1");          // 将showMouseAnimation设为1，表示打开鼠标动画
      document.querySelector("body").addEventListener("click", mouseAnimation);  // 给body元素添加一个点击事件监听器，当点击body元素时，调用mouseAnimation函数
      showMessage("哈哈，要牢记社会主义核心价值观哦！", 6000, 9);  // 显示消息6秒，优先级设为9
    } else {
      localStorage.setItem("showMouseAnimation", "0");          // 如果本地存储中当前鼠标动画处于打开状态，则将showMouseAnimation设为0，表示关闭鼠标动画
      document.querySelector("body").removeEventListener("click", mouseAnimation);
      showMessage("今天你爱国了吗？", 6000, 9);                  // 显示消息6秒，优先级设为9
    }
  }

  /**
  * @author DavidLee 
  * @title mouseAnimation 
  * @date 2024-10-11
  * @description 在网页上实现鼠标移动时显示动画效果（具体来说，当鼠标在页面上移动时，会在鼠标位置附近显示社会主义核心价值观中的一个词语，并在一定时间后逐渐消失）
  * @param  e：鼠标坐标位置
  * @return null
  */
  function mouseAnimation(e) {
    let list = new Array("富强", "民主", "文明", "和谐", "自由", "平等", "公正", "法治", "爱国", "敬业", "诚信", "友善");
    let span = $("<span>").text(list[idx]);  // 创建并设置<span>元素，元素中包含list中的其中一个词语
    idx = (idx + 1) % list.length;           // 循环消息索引值
    let x = e.pageX, y = e.pageY;            // 获取鼠标位置
    span.css({                               // 设置<span>元素的样式
      "z-index": 1000,
      "top": y - 20,
      "left": x,
      "position": "absolute",
      "pointer-events": "none",
      "font-weight": "bold",
      "color": "#ff6651"
    });
    $("body").append(span);                 // 将<span>元素添加到body元素中，使其显示在页面上
    span.animate({"top": y - 180, "opacity": 0}, 1500, function () {    // 使用jQuery的animate方法，使<span>元素在1.5秒内向上移动并逐渐消失
      span.remove();                                                    // 动画完成后，<span>元素被从页面上移除
    });
  }

  /**
  * @author DavidLee 
  * @title randomSelection 
  * @date 2024-10-11
  * @description 从给定的对象中随机选择一个元素
  * @param  obj：数组或对象
  * @return null
  */
  function randomSelection(obj) {
    // 首先判断传入的参数obj是否是一个数组，如果是，则从数组中随机选择介于0和数组长度之间的随机整数并返回，如果不是数组，则直接返回obj
    return Array.isArray(obj) ? obj[Math.floor(Math.random() * obj.length)] : obj;
  }

  /**
  * @author DavidLee 
  * @title showHitokoto 
  * @date 2024-10-11
  * @description 从hitokoto.cn的API获取一句随机的名言或句子并在网页上显示
  * @param  null
  * @return null
  */
  function showHitokoto() {
    fetch(constant.hitokoto)  // 增加 hitokoto.cn 的 API（发送请求到hitokoto.cn）
      .then(response => response.json())  // 将响应转换为 JSON 格式
      .then(result => {
        // 从响应中获取名言或句子的出处，并创建一个包含该句子的字符串
        const text = `这句一言来自 <span>「${result.from}」</span>，是 <span>${result.creator}</span> 在 hitokoto.cn 投稿的。`;  
        showMessage(result.hitokoto, 6000, 9);  // 显示名言或句子6秒，优先级设为9
        setTimeout(() => {
          showMessage(text, 4000, 9);           // 6秒后显示名言出处4秒，优先级设为9
        }, 6000);
      });
  }

  /**
  * @author DavidLee 
  * @title showMessage 
  * @date 2024-10-11
  * @description 在网页上显示一条消息，并在一段时间后自动隐藏
  * @param  text：要显示的消息内容
  * @param  timeout：消息显示的时间
  * @param  priority：消息的优先级，用于决定是否显示这条消息。如果当前存储的消息优先级更高，则不显示新消息
  * @return null
  */
  function showMessage(text, timeout, priority) {
    // 首先检查text参数是否为空。如果为空，则直接返回，不显示任何消息
    // 然后看本地存储中是否已经存储了一条消息及其优先级，如果已经存储了一条消息且其优先级高于当前消息，则不显示当前消息
    if (!text || (localStorage.getItem("waifu-text") && localStorage.getItem("waifu-text") > priority)) return;
    // 检查messageTimer定时器是否已经设置。如果已设置，则清除当前定时器，防止多个定时器同时运行
    if (messageTimer) {
      clearTimeout(messageTimer);
      messageTimer = null;
    }
    text = randomSelection(text);                        // 从text中随机选择一个元素
    localStorage.setItem("waifu-text", priority);        // 将当前消息的优先级存储到本地存储中
    const tips = document.getElementById("waifu-tips");  // 获取页面上的一个元素（waifu-tips），
    tips.innerHTML = text;                               // 将waifu-tips内容设置为当前消息
    tips.classList.add("waifu-tips-active");             // 添加一个CSS类（waifu-tips-active）以显示消息
    messageTimer = setTimeout(() => {                    // 设置定时器，在timeout时间后从本地存储中移除当前waifu-text消息和对应的CSS类
      localStorage.removeItem("waifu-text");
      tips.classList.remove("waifu-tips-active");
    }, timeout);
  }
}
